﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Data;
using System.IO;

namespace Sunny.Lib
{
    public class XMLHelper
    {
        #region Insert
        /// <summary>
        /// This method will overwrite content to the file.
        /// </summary>
        /// <typeparam name="F"></typeparam>
        /// <param name="fi"></param>
        /// <param name="noteList"></param>
        /// <returns></returns>
        public static ResultMessage SaveXML_DataSet<F>(F fi, Dictionary<string, string> noteList) where F : XMLFileInfo
        {
            ResultMessage result = new ResultMessage();

            try
            {
                System.Data.DataSet ds = new DataSet();
                ds.DataSetName = fi.DataSetName;
                DataTable dt = new DataTable();
                dt.TableName = fi.DataTableName;
                ds.Tables.Add(dt);

                var fieldList = fi.AllFields.Split(new char[] { ',' });
                foreach (var field in fieldList)
                {
                    dt.Columns.Add(field, Type.GetType("System.String"));
                }

                DataRow dr = dt.NewRow();
                bool dataIsNull = (noteList == null);
                foreach (var field in fieldList)
                {
                    if (!dataIsNull && noteList.ContainsKey(field))
                    {
                        dr[field] = noteList[field];
                        dt.Rows.Add(dr);
                    }
                }

                ds.WriteXml(fi.GetXMLFileFullName());

                result.Status = ResultStatus.Success;
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message;
                result.ResultDescription += ex.StackTrace;
            }

            return result;
        }

        /// <summary>
        /// This method will overwrite content to the file.
        /// </summary>
        /// <typeparam name="F"></typeparam>
        /// <param name="fi"></param>
        /// <param name="noteList"></param>
        /// <returns></returns>
        public static string SaveXMLData_XmlTextWriter<F>(F fi, Dictionary<string, string> noteList) where F : XMLFileInfo
        {
            try
            {
                //创建一个XmlTextWriter类的实例对象
                System.IO.FileInfo file = new FileInfo(fi.GetXMLFileFullName());
                //if (!file.Exists)
                XmlTextWriter textWriter = new XmlTextWriter(fi.GetXMLFileFullName(), System.Text.Encoding.UTF8);
                //开始写过程，调用WriterStartDocument方法写入文件头信息
                //例如<?xml version="1.0" encoding="utf-8"?>
                textWriter.WriteStartDocument();
                //写入根节点<phonebook></phonebook>
                textWriter.WriteStartElement(fi.DataSetName);
                bool dataIsNull = (noteList == null);
                if (!dataIsNull)
                {
                    textWriter.WriteStartElement(fi.DataTableName);
                }
                //写入节点
                var fieldList = fi.AllFields.Split(new char[] { ',' });

                foreach (var field in fieldList)
                {
                    if (!dataIsNull && noteList.ContainsKey(field))
                    {
                        textWriter.WriteStartElement(field, "");
                        textWriter.WriteString(noteList[field]);
                        textWriter.WriteEndElement();
                    }
                }

                //写入文档结束，调用WriteEndDocument方法
                textWriter.WriteEndDocument();
                //关闭textWriter
                textWriter.Close();
                return "true";
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }

        /// <summary>
        /// Add the row data to xml. append to the last.
        /// </summary>
        /// <typeparam name="F">xml file info type</typeparam>
        /// <param name="noteList">row data</param>
        /// <param name="fi">xml file info</param>
        /// <returns></returns>
        public static ResultMessage SaveXMLData_XmlNode<F>(F fi, Dictionary<string, object> noteList) where F : XMLFileInfo
        {
            ResultMessage result = new ResultMessage();

            try
            {
                System.Xml.XmlDocument xdoc = new XmlDocument();
                xdoc.Load(fi.GetXMLFileFullName());

                XmlElement parentNode = xdoc.CreateElement(fi.DataTableName);
                xdoc.DocumentElement.AppendChild(parentNode);              // append to the last

                // Create the parentNode's all children node
                var fieldList = fi.AllFields.Split(new char[] { ',' });
                foreach (var field in fieldList)
                {
                    XmlElement fieldNode = xdoc.CreateElement(field);
                    if (noteList.ContainsKey(field))
                    {
                        fieldNode.InnerText = noteList[field].ToString ();
                    }
                    parentNode.AppendChild(fieldNode);
                }

                xdoc.Save(fi.GetXMLFileFullName());
                result.Status = ResultStatus.Success;
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message;
                result.ResultDescription += ex.StackTrace;
            }

            return result;
        }
        #endregion

        #region Read

        /// <summary>
        /// Get the value by xmlnode name
        /// </summary>
        /// <param name="keyName">find node name</param>
        /// <param name="xmlFullPath">xml file full path</param>
        /// <returns>List<string></returns>
        public static List<string> GetXML_XmlNodeReader(string keyName, string xmlFullPath)
        {
            List<string> findList = new List<string>();
            XmlDocument xml = new XmlDocument();
            xml.Load(xmlFullPath);

            XmlNodeReader read = new XmlNodeReader(xml);
            while (read.Read())
            {
                if (read.NodeType == System.Xml.XmlNodeType.Element && read.LocalName.ToString().Equals(keyName))
                {
                    read.Read();
                    if (read.NodeType == System.Xml.XmlNodeType.Text)
                    {
                        findList.Add(read.Value);
                    }
                }
            }

            return findList;
        }

        /// <summary>
        /// Is exist a node that it's name = keyName and value = keyValue
        /// </summary>
        /// <param name="keyName">node name</param>
        /// <param name="keyValue">node value</param>
        /// <param name="xmlFullPath">xml file full path </param>
        /// <returns>exist:true;otherwise:false</returns>
        public static bool IsExistKeyValue(string keyName, string keyValue, string xmlFullPath)
        {
            bool isExist = false;

            XmlDocument xml = new XmlDocument();
            xml.Load(xmlFullPath);

            XmlNodeReader read = new XmlNodeReader(xml);
            while (read.Read())
            {
                if (read.NodeType == System.Xml.XmlNodeType.Element && read.LocalName.ToString().Equals(keyName))
                {
                    read.Read();
                    if (read.NodeType == System.Xml.XmlNodeType.Text)
                    {
                        if (keyValue.Equals(read.Value, StringComparison.OrdinalIgnoreCase))
                        {
                            isExist = true;
                            break;
                        }
                    }
                }
            }

            return isExist;
        }

        /// <summary>
        /// Get the all note that meeting the conditions
        /// </summary>
        /// <typeparam name="F">xml file info type</typeparam>
        /// <param name="fi">xml file info</param>
        /// <param name="queryConditions">conditions</param>
        /// <returns>DataSet</returns>
        public static DataSet GetXML_DataSet<F>(F fi, List<QueryCondition> queryConditions) where F : XMLFileInfo
        {
            DataSet ds = new DataSet();
            ds.ReadXml(fi.GetXMLFileFullName());

            DataSet dataSet = new DataSet();
            if (queryConditions == null)
            {
                dataSet = ds;
            }
            else
            {
                DataTable dt = new DataTable() { TableName = fi.DataTableName };

                if (ds.Tables.Count > 0 && ds.Tables[fi.DataTableName] != null && ds.Tables[fi.DataTableName].Rows.Count > 0)
                {
                    dt = ds.Tables[fi.DataTableName].Clone();
                    string condition = QueryCondition.GetWhereStr(queryConditions, string.Empty);
                    DataRow[] dataRows = ds.Tables[fi.DataTableName].Select(condition);

                    foreach (DataRow row in dataRows)
                    {
                        dt.ImportRow(row);
                    }
                }
                dataSet.Tables.Add(dt);
            }

            return dataSet;
        }
        #endregion

        #region Update
        /// <summary>
        /// Modify the xml node's InnerText value
        /// </summary>
        /// <param name="nodeList">Dictionary<string, string>:NodeName,NodeValue pair</param>
        /// <param name="fi">The information of xml file you want to edit </param>
        /// <returns></returns>
        public static ResultMessage Modify_XmlNodeValue(XMLFileInfo fi, Dictionary<string, object> nodeList, string keyValue = null)
        {
            ResultMessage result = new ResultMessage();

            try
            {
                XmlDocument myDoc = new XmlDocument();
                myDoc.Load(fi.GetXMLFileFullName());

                // Search the special node
                System.Xml.XmlNodeList nodes = myDoc.SelectNodes(fi.DataSetName + "/" + fi.DataTableName);

                var fieldList = fi.UpdateFields.Split(new char[] { ',' });
                if (nodes != null)
                {
                    foreach (System.Xml.XmlNode xn in nodes)
                    {
                        string keyvalue = string.IsNullOrEmpty(keyValue) ? nodeList[fi.PrimaryKey].ToString () : keyValue;
                        if (xn.SelectSingleNode(fi.PrimaryKey).InnerText.Trim() == keyvalue)
                        {
                            foreach (var field in fieldList)
                            {
                                if (nodeList.ContainsKey(field))
                                {
                                    xn.SelectSingleNode(field).InnerText = nodeList[field].ToString ();
                                }
                            }
                        }
                    }
                }

                myDoc.Save(fi.GetXMLFileFullName());
                result.Status = ResultStatus.Success;
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message;
                result.ResultDescription += ex.StackTrace;
            }

            return result;
        }

        /// <summary>
        /// Update xml node name
        /// </summary>
        /// <param name="oldName">old node name</param>
        /// <param name="newName">new node name</param>
        /// <param name="xmlFileFullPath">xml file full path</param>
        /// <returns></returns>
        public static ResultMessage Modify_XmlNodeName(string oldName, string newName, string xmlFileFullPath)
        {
            ResultMessage result = new ResultMessage();

            #region parameter pre-check
            if (string.IsNullOrEmpty(oldName) || string.IsNullOrWhiteSpace(oldName))
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription = "Old name could not be null/empty/whitespace";
                return result;
            }

            if (string.IsNullOrEmpty(newName) || string.IsNullOrWhiteSpace(newName))
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription = "New name could not be null/empty/whitespace";
                return result;
            }

            oldName = oldName.Trim();
            newName = newName.Trim();

            if (oldName.Equals(newName, StringComparison.Ordinal))
            {
                result.Status = ResultStatus.Success;
                return result;
            }
            #endregion

            try
            {
                //System.IO.FileStream xmlFile = new FileStream(XMLFileInfo.GetXMLFileFullName(fi.XMLFileName),FileMode.Open, FileAccess.ReadWrite);
                System.IO.StreamReader xmlFile = new System.IO.StreamReader(xmlFileFullPath);

                string line;
                string oldStartTag = "<" + oldName;
                string oldEndTag = "</" + oldName;
                string newStartTag = "<" + newName;
                string newEndTag = "</" + newName;
                bool isChange = false;
                StringBuilder xmlString = new StringBuilder();
                while ((line = xmlFile.ReadLine()) != null)
                {

                    if (line.IndexOf(oldStartTag) > 0)
                    {
                        line = line.Replace(oldStartTag, newStartTag);
                        isChange = true;
                    }
                    if (line.IndexOf(oldEndTag) > 0)
                    {
                        line = line.Replace(oldEndTag, newEndTag);
                        isChange = true;
                    }

                    xmlString.Append(line);
                }

                xmlFile.Dispose();

                if (isChange)
                {
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(xmlString.ToString());
                    xmlDoc.Save(xmlFileFullPath);
                }
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message + ex.StackTrace;
                return result;
            }

            result.Status = ResultStatus.Success;
            return result;
        }

        /// <summary>
        /// Only for root xml modify
        /// </summary>
        /// <param name="newF">the value before update</param>
        /// <param name="oldF">the value after update</param>
        /// <param name="root"></param>
        /// <returns></returns>
        public static ResultMessage Modify_Root_XmlNodeValue(XMLFileInfo root, XMLFileInfo oldF, XMLFileInfo newF)
        {
            ResultMessage result = new ResultMessage();
            if (!newF.DataTableName.Equals(oldF.DataTableName))
            {
                result = Modify_XmlNodeName(oldF.DataTableName, newF.DataTableName, oldF.GetXMLFileFullName());
                if (result.Status == ResultStatus.Failed)
                {
                    return result;
                }
            }

            Dictionary<string, object> nodelist = ReflectionHelper.GetPropertiesKeyValuePairs<XMLFileInfo>(newF, System.Reflection.BindingFlags.Instance |
                                                                                                                    System.Reflection.BindingFlags.Public);

            result = Modify_XmlNodeValue(root, nodelist);

            return result;
        }
        #endregion

        #region Delete
        /// <summary>
        /// Delete the special node that node name = delConditionPropertyName and node value = delConditionPropertyValue
        /// </summary>
        /// <typeparam name="F"></typeparam>
        /// <param name="fi"></param>
        /// <param name="delConditionPropertyName">Node name</param>
        /// <param name="delConditionPropertyValue">Node value</param>
        /// <returns></returns>
        public static ResultMessage Delete_XmlNode<F>(F fi, string delConditionPropertyName, string delConditionPropertyValue) where F : XMLFileInfo
        {
            ResultMessage result = new ResultMessage();

            try
            {
                XmlDocument myDoc = new XmlDocument();
                myDoc.Load(fi.GetXMLFileFullName());

                // search the special node
                System.Xml.XmlNodeList nodes = myDoc.SelectNodes("/" + fi.DataSetName + "/" + fi.DataTableName);

                if (nodes != null)
                {
                    foreach (System.Xml.XmlNode xn in nodes)
                    {
                        if (xn.ChildNodes[0].InnerText.Trim() == delConditionPropertyValue)
                        {
                            xn.ParentNode.RemoveChild(xn);
                        }
                    }
                }

                myDoc.Save(fi.GetXMLFileFullName());
                result.Status = ResultStatus.Success;
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message;
                result.ResultDescription += ex.StackTrace;
            }

            return result;
        }
        #endregion
    }

    public class XMLFileInfo
    {
        public string XMLFileName { get; set; }
        public string DataSetName { get; set; }
        public string DataTableName { get; set; }
        public string AllFields { get; set; }
        public string UpdateFields { get; set; }
        public string QueryFields { get; set; }
        public string PrimaryKey { get; set; }
        public string Classification { get; set; }
        public string Descriptions { get; set; }

        public XMLFileInfo()
        {
        }

        public XMLFileInfo(string fileName)
        {
            this.XMLFileName = GetXMLFileName(fileName);
        }

        public ResultMessage LoadXMLFileInfo()
        {
            ResultMessage result = new ResultMessage();
            try
            {
                bool isExist = false;
                DataSet ds = new DataSet();
                ds.ReadXml(System.IO.Path.Combine(EnvironmentHelper.CurrentDir, ConfigurationHelper.RootXml));
                if (ds.Tables.Count > 0 && ds.Tables[XMLFileInfoConstant.FileInfo] != null && ds.Tables[XMLFileInfoConstant.FileInfo].Rows.Count > 0)
                {
                    foreach (DataRow row in ds.Tables[XMLFileInfoConstant.FileInfo].Rows)
                    {
                        if (this.XMLFileName.Equals(row[XMLFileInfoConstant.XMLFileName].ToString(), StringComparison.OrdinalIgnoreCase))
                        {
                            this.AllFields = row[XMLFileInfoConstant.AllFields].ToString();
                            this.QueryFields = row[XMLFileInfoConstant.QueryFields].ToString();
                            this.UpdateFields = row[XMLFileInfoConstant.UpdateFields].ToString();
                            this.PrimaryKey = row[XMLFileInfoConstant.PrimaryKey].ToString();
                            this.DataSetName = row[XMLFileInfoConstant.DataSetName].ToString();
                            this.DataTableName = row[XMLFileInfoConstant.DataTableName].ToString();
                            this.Classification = row[XMLFileInfoConstant.Classification].ToString();
                            this.Descriptions = row[XMLFileInfoConstant.Descriptions].ToString();

                            isExist = true;
                            break;
                        }
                    }
                }
                if (isExist)
                {
                    result.Status = ResultStatus.Success;
                }
                else
                {
                    result.Status = ResultStatus.Failed;
                    result.ResultDescription += string.Format("Can't find the xml by the xml name:\"{0}\"", this.XMLFileName);
                }
            }
            catch (Exception ex)
            {
                result.Status = ResultStatus.Failed;
                result.ResultDescription += ex.Message;
                result.ResultDescription += ex.StackTrace;
            }

            return result;
        }

        public string GetXMLFileFullName()
        {
            return System.IO.Path.Combine(EnvironmentHelper.CurrentDir, XMLFileName);
        }

        private string GetXMLFileName(string fileName)
        {
            string xmlFileName = fileName;

            if (!fileName.Contains("XML/"))
            {
                xmlFileName = "XML/" + fileName + ".xml";
            }

            return xmlFileName;
        }
    }

    public class XMLFileInfoConstant
    {
        public static string XMLFileName = "XMLFileName";
        public static string DataSetName = "DataSetName";
        public static string DataTableName = "DataTableName";
        public static string AllFields = "AllFields";
        public static string UpdateFields = "UpdateFields";
        public static string QueryFields = "QueryFields";
        public static string PrimaryKey = "PrimaryKey";
        public static string Descriptions = "Descriptions";
        public static string Classification = "Classification";

        public static string FileInfos = "XmlFileInfos";
        public static string FileInfo = "XmlFileInfo";
    }
}
